import { Injectable } from '@nestjs/common'
import Yuque from '@yuque/sdk'
import { DocHistoryRepo, DocRepo, DocTocRepo } from '@/entities'
import { CustomLoggerService, NRedis } from '@/modules'
import { IYuqueDoc } from '@/interface'
import { timestampFormat, uuid } from '@/util'
import request from 'umi-request'
import md5 from 'md5'
import { RType } from '@/modules/redis/interface'

const api = 'https://bty-doc.yuque.com/api/v2'
const token = 'G2vA2Q5EeJj6er6MBGBG9kJjrkZRAMEVqhNUpowX'
const userAgent = 'bty-doc'

@Injectable()
export class DocService {
    private readonly yuque = new Yuque({
        endpoint: 'https://www.yuque.com/api/v2/',
        token,
        userAgent,
    })
    constructor(
        private readonly docRepo: DocRepo,
        private readonly docTocRepo: DocTocRepo,
        private readonly docHistoryRepo: DocHistoryRepo,
        private readonly redis: NRedis,
        private readonly logger: CustomLoggerService,
    ) {}

    public async notify(option: IYuqueDoc) {
        if (option.action_type === 'delete') {
            await this.docRepo.delete({ docid: option.id })
            return {}
        }
        const now = timestampFormat()
        const context = option.body_html
        const [docTitle, companyName] = option.title.split(':')
        const auInfo = await this.yuque.users.get({ login: option.user_id })
        const edInfo = await this.yuque.users.get({ login: option.actor_id })
        const exiting = await this.docRepo.findOne({ docid: option.id })
        if (exiting) {
            await this.docHistoryRepo.save(
                this.docHistoryRepo.create({
                    docid: exiting.docid,
                    oldPath: exiting.docPath,
                    oldContext: exiting.context,
                    newPath: option.path,
                    newContext: context,
                    au: edInfo.name || '未知',
                    ct: now,
                }),
            )
            exiting.context = context
            exiting.docPath = option.path
            exiting.docTitle = docTitle
            exiting.ut = now
            exiting.ed = edInfo.name || '未知'
            await this.docRepo.save(exiting)
        } else {
            await this.docRepo.save(
                this.docRepo.create({
                    docid: option.id,
                    docPath: option.path,
                    docTitle,
                    context,
                    companyName: companyName || '贝图云',
                    ct: now,
                    ut: now,
                    au: auInfo.name || '未知',
                    ed: edInfo.name || '未知',
                }),
            )
        }
        await this.redis.deleteKeys(RType.YUQUE_CACHE_DOC + '*')
        this.formatToc(option.book_id)
            .then((v) => {
                this.logger.info(`[DocService] notify 更新目录成功：${v}条`)
            })
            .catch((e) => {
                this.logger.info(`[DocService] notify 更新目录失败：${e.message}`)
            })
        return {}
    }

    /**
     * 更新目录
     * @param book_id
     */
    public async formatToc(book_id: number) {
        const { data } = await request(`${api}/repos/${book_id}/toc`, {
            headers: {
                'X-Auth-Token': token,
                'User-Agent': userAgent,
            },
        })
        if (data.length > 0) {
            const now = timestampFormat()
            await this.docTocRepo.clear()
            const repos = []
            for await (const toc of data) {
                repos.push(
                    this.docTocRepo.create({
                        tocid: toc.uuid,
                        previd: toc.prev_uuid,
                        siblingid: toc.sibling_uuid,
                        parentid: toc.parent_uuid,
                        docid: toc.doc_id,
                        tocTitle: toc.title,
                        tocType: toc.type === 'DOC' ? 1 : 0,
                        ct: now,
                        ut: now,
                        au: 'sync',
                    }),
                )
            }
            await this.docTocRepo.save(repos)
            await this.redis.deleteKeys(RType.YUQUE_CACHE_TOC + '*')
            return repos.length
        }
        throw new Error('未获取到目录！')
    }

    /**
     * 查询手册内容
     * @param docid
     * @param docTitle
     * @param companyName
     */
    public async queryDoc(docid: string, docTitle: string, companyName = '贝图云') {
        const hash = md5(docid || `${docTitle}:${companyName}`)
        let cache = await this.redis.get(RType.YUQUE_CACHE_DOC + hash)
        if (cache) return cache
        if (docid) {
            cache = await this.docRepo.findOne({ docid })
        } else if (docTitle) {
            cache = await this.docRepo.findOne({ docTitle, companyName })
        }
        await this.redis.set(RType.YUQUE_CACHE_DOC + hash, cache, 3600)
        return { data: cache }
    }

    /**
     * 查询目录
     * @param companyName
     */
    public async queryToc(companyName = '贝图云') {
        const cache = await this.redis.get(RType.YUQUE_CACHE_TOC + companyName)
        if (cache) return cache
        const tocs = await this.docTocRepo
            .createQueryBuilder('t')
            .leftJoin('doc', 'd', 'd.docid=t.docid')
            .where('d.companyName=:companyName', { companyName })
            .orWhere('d.companyName=:bty', { bty: '贝图云' })
            .orWhere('t.parentid=""')
            .getMany()
        const toc = this.listToTree('', tocs)
        await this.redis.set(RType.YUQUE_CACHE_TOC + companyName, toc, 3600)
        return toc
    }

    private listToTree(tocid, tocs) {
        const children = tocs.filter((o) => o.parentid === tocid)
        for (const item of children) {
            item.children = this.listToTree(item.tocid, tocs)
        }
        return children
    }
}
